package cn.caplike.demo.repository.annotation.processor.ast;

import com.google.auto.service.AutoService;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.net.URL;
import java.util.Set;

@SupportedAnnotationTypes({"cn.caplike.demo.repository.annotation.processor.ast.TheAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
@SuppressWarnings("unused")
public class TheProcessor extends AbstractProcessor {

    /**
     * 抽象语法树
     */
    private JavacTrees trees;

    /**
     * AST
     */
    private TreeMaker treeMaker;

    /**
     * 标识符
     */
    private Names names;

    private Messager messager;

    private Filer filer;

    private final Kit KIT = new Kit();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        final Context context = ((JavacProcessingEnvironment) processingEnvironment).getContext();

        this.trees = JavacTrees.instance(processingEnvironment);
        this.treeMaker = TreeMaker.instance(context);
        this.messager = processingEnvironment.getMessager();
        this.names = Names.instance(context);
        this.filer = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        roundEnv.getElementsAnnotatedWith(TheAnnotation.class).forEach(element -> {
            final ElementKind kind = element.getKind();
            if (!ElementKind.CLASS.equals(kind))
                error("Unsupported kind: " + kind);

            final TheAnnotation theAnnotation = element.getAnnotation(TheAnnotation.class);
            final String value = theAnnotation.value();
            note("TheAnnotation's value: " + value);

            trees.getTree(element).accept(new TreeTranslator() {
                @Override
                public void visitClassDef(JCTree.JCClassDecl classDecl) {
                    super.visitClassDef(classDecl);

                    // 生成内部类， 并将内部类写进去
                    JCTree.JCClassDecl innerClass = KIT.generateInnerClass(value);
                    classDecl.defs = classDecl.defs.append(innerClass);
                }

                @Override
                public void visitMethodDef(JCTree.JCMethodDecl methodDecl) {
                    super.visitMethodDef(methodDecl);

                    final String methodName = methodDecl.name.toString();
                    System.out.println("MethodName: " + methodName);

                    final JCTree.JCBlock body = methodDecl.getBody();
                    body.getStatements().forEach(statement -> {
                        System.out.println("- statement: " + statement.toString());

                    });
                }
            });
        });

        return false;
    }

    private final class Kit {
        JCTree.JCClassDecl generateInnerClass(final String value) {
            final JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC);
            final Name name = names.fromString(value);
            JCTree.JCClassDecl jcClassDecl1 = treeMaker.ClassDef(modifiers, name, List.nil(), null, List.nil(), List.nil());
//            List<JCTree.JCVariableDecl> collection = generateAllParameters(paramsInfoMap, paramsNameTypeMap);
//            collection.forEach(x -> jcClassDecl1.defs = jcClassDecl1.defs.append(x));
            return jcClassDecl1;
        }
    }

    private void error(String message) {
        messager.printMessage(Diagnostic.Kind.ERROR, message);
        System.err.println(message);
    }

    private void note(String message) {
        messager.printMessage(Diagnostic.Kind.NOTE, message);
        System.out.println(message);
    }
}
